import logging
import logging.handlers
import os
import stat
from configparser import ConfigParser
from os.path import join, exists
from typing import NoReturn

from log.constants.choices import LogType
from log.constants.paths import LOG_CONFIG_DIR, LOG_DIR
from log.exceptions import LogTypeError


class LoggerGenerator:

    def __init__(self, log_type=LogType.USER.value):
        if log_type not in LogType.get_log_types():
            raise LogTypeError(log_type)
        self.name = log_type
        self.config = ConfigParser()
        self.config.read(join(LOG_CONFIG_DIR, f"{log_type}.conf"))
        self.logger_level = self._get_level("logger")
        self.console_handler_level = self._get_level("console_handler")
        self.console_handler_format = self.config.get("console_handler", "format",
                                                      fallback='%(asctime)s - %(levelname)s - %(message)s')
        self.file_handler_level = self._get_level("file_handler")
        self.file_handler_format = self.config.get("file_handler", "format",
                                                   fallback='%(asctime)s - %(levelname)s - %(message)s')
        self.file_handler_max_size = self.config.getint("file_handler", "max_size", fallback=5 * 1024 * 1024)
        self.file_handler_backup_count = self.config.getint("file_handler", "backup_count", fallback=5)
        self.log_dir = self.config.get("file_handler", "log_dir", fallback=LOG_DIR)

    def get_logger(self) -> logging.Logger:
        """
        获取日志记录器，并且添加日志记录器的日志处理器，日志处理器类型如下所示：
            - 控制台日志处理器
            - 文件日志处理器
        :return: 日志记录器
        """
        logger = logging.getLogger(self.name)
        logger.setLevel(self.logger_level)
        # 判断是否已经添加控制台日志处理器，如果未添加则添加新的控制台日志处理器
        if not self._has_console_handler(logger):
            self._add_console_handler(logger)
        # 判断是否已经添加文件日志处理器，如果未添加则添加新的文件日志处理器
        if not self._has_file_handler(logger):
            self._add_file_handler(logger)
        return logger

    @staticmethod
    def _has_console_handler(logger: logging.Logger) -> bool:
        """
        判断日志记录器是否添加了控制台日志处理器
        :param logger: 日志记录器
        :return: 布尔值，是否已添加控制台日志处理器
        """
        return any(isinstance(h, logging.StreamHandler) for h in logger.handlers)

    @staticmethod
    def _has_file_handler(logger: logging.Logger) -> bool:
        """
        判断日志记录器是否添加了文件日志处理器
        :param logger: 日志记录器
        :return: 布尔值，是否已添加文件日志处理器
        """
        return any(isinstance(h, logging.handlers.RotatingFileHandler) for h in logger.handlers)

    def _add_console_handler(self, logger: logging.Logger) -> NoReturn:
        """
        添加控制台日志处理器
        :param logger: 日志记录器
        :return: None
        """
        console_handler = logging.StreamHandler()
        console_handler.setLevel(self.console_handler_level)
        console_formatter = logging.Formatter(self.console_handler_format)
        console_handler.setFormatter(console_formatter)
        logger.addHandler(console_handler)

    def _add_file_handler(self, logger: logging.Logger) -> NoReturn:
        """
        添加文件日志处理器
        :param logger: 日志记录器
        :return: None
        """
        if not exists(self.log_dir):
            os.makedirs(self.log_dir, exist_ok=True)
            os.chmod(self.log_dir, stat.S_IRWXU)
        file_handler = logging.handlers.RotatingFileHandler(
            join(self.log_dir, f"{self.name}.log"),
            maxBytes=self.file_handler_max_size,
            backupCount=self.file_handler_backup_count,
        )
        file_handler.setLevel(self.file_handler_level)
        file_formatter = logging.Formatter(self.file_handler_format)
        file_handler.setFormatter(file_formatter)
        logger.addHandler(file_handler)

    def _get_level(self, section: str) -> int:
        """
        从配置文件中获取日志级别，如果配置文件中未定义，则返回默认日志级别 INFO
        :param section: 要获取的日志级别的主体，可能是日志记录器或者日志处理器
        :return: 日志级别
        """
        level = self.config.get(section, "level", fallback="INFO")
        level_mapping = {
            "CRITICAL": logging.CRITICAL,
            "ERROR": logging.ERROR,
            "WARNING": logging.WARNING,
            "INFO": logging.INFO,
            "DEBUG": logging.DEBUG,
            "NOTSET": logging.NOTSET,
        }
        return level_mapping.get(level.upper(), logging.INFO)
